package com.beowulfe.hap.characteristics;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for implementing {@link Characteristic}.
*
* @author Andy Lintner
*/
public abstract class BaseCharacteristic<T> implements Characteristic {
private final Logger logger = LoggerFactory.getLogger(BaseCharacteristic.class);
private final String type;
private final String format;
private final boolean isWritable;
private final boolean isReadable;
private final boolean isEventable;
private final String description;
/**
* Default constructor
*
* @param type a string containing a UUID that indicates the type of characteristic. Apple defines a set of these,
* however implementors can create their own as well.
* @param format a string indicating the value type, which must be a recognized type by the consuming device.
* @param isWritable indicates whether the value can be changed.
* @param isReadable indicates whether the value can be retrieved.
* @param description a description of the characteristic to be passed to the consuming device.
*/
public BaseCharacteristic(String type, String format, boolean isWritable, boolean isReadable, String description) {
if (type == null || format == null || description == null) {
throw new NullPointerException();
}
this.type = type;
this.format = format;
this.isWritable = isWritable;
this.isReadable = isReadable;
this.isEventable = this instanceof EventableCharacteristic;
this.description = description;
}
@Override
/**
* {@inheritDoc}
*/
public final CompletableFuture<JsonObject> toJson(int iid) {
return makeBuilder(iid).thenApply(builder -> builder.build());
}
/**
* Creates the JSON serialized form of the accessory for use over the Homekit Accessory Protocol.
*
* @param instanceId the static id of the accessory.
* @return a future that will complete with the JSON builder for the object.
*/
protected CompletableFuture<JsonObjectBuilder> makeBuilder(int instanceId) {
return getValue().exceptionally(t -> {
logger.error("Could not retrieve value "+this.getClass().getName(), t);
return null;
}).thenApply(value -> {
JsonArrayBuilder perms = Json.createArrayBuilder();
if (isWritable) {
perms.add("pw");
}
if (isReadable) {
perms.add("pr");
}
if (isEventable) {
perms.add("ev");
}
JsonObjectBuilder builder = Json.createObjectBuilder()
.add("iid", instanceId)
.add("type", type)
.add("perms", perms.build())
.add("format", format)
.add("events", false)
.add("bonjour", false)
.add("description", description);
setJsonValue(builder, value);
return builder;
});
}
/**
* {@inheritDoc}
*/
@Override
public final void setValue(JsonValue jsonValue) {
try {
this.setValue(convert(jsonValue));
} catch (Exception e) {
logger.error("Error while setting JSON value", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void supplyValue(JsonObjectBuilder builder) {
try {
setJsonValue(builder, getValue().get());
} catch (InterruptedException | ExecutionException e) {
logger.error("Error retrieving value", e);
setJsonValue(builder, getDefault());
}
}
/**
* Converts from the JSON value to a Java object of the type T
*
* @param jsonValue the JSON value to convert from.
* @return the converted Java object.
*/
protected abstract T convert(JsonValue jsonValue);
/**
* Update the characteristic value using a new value supplied by the connected client.
*
* @param value the new value to set.
* @throws Exception if the value cannot be set.
*/
protected abstract void setValue(T value) throws Exception;
/**
* Retrieves the current value of the characteristic.
*
* @return a future that will complete with the current value.
*/
protected abstract CompletableFuture<T> getValue();
/**
* Supplies a default value for the characteristic to send to connected clients when the real value.
* cannot be retrieved.
*
* @return a sensible default value.
*/
protected abstract T getDefault();
/**
* Writes the value key to the serialized characteristic
*
* @param builder The JSON builder to add the value to
* @param value The value to add
*/
protected void setJsonValue(JsonObjectBuilder builder, T value) {
//I don't like this - there should really be a way to construct a disconnected JSONValue...
if (value instanceof Boolean) {
builder.add("value", (Boolean) value);
} else if (value instanceof Double) {
builder.add("value", (Double) value);
} else if (value instanceof Integer) {
builder.add("value", (Integer) value);
} else if (value instanceof Long) {
builder.add("value", (Long) value);
} else if (value instanceof BigInteger) {
builder.add("value", (BigInteger) value);
} else if (value instanceof BigDecimal){
builder.add("value", (BigDecimal) value);
} else if (value == null) {
// Do not add null value, HomeKit cannot handle that
} else {
builder.add("value", value.toString());
}
}
}